#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <err.h>


#define ENCODE_ACTION	1
#define DECODE_ACTION	2
#define DEFAULT_ACTION	1

#define FILE_BUFFER     2048

void usage(char* pgname)
{
	fprintf(stdout,"Usage:\n");
	fprintf(stdout,"%s -h\n",pgname);
	fprintf(stdout,"%s -V\n",pgname);
	fprintf(stdout,"%s [options]\n",pgname);

	fprintf(stdout,"Options:\n");
	fprintf(stdout,"%s -V, --version\t\t\tPrint version and quit.\n",pgname);		
	fprintf(stdout,"%s -h, --help\t\t\tPrint this information.\n",pgname);

	fprintf(stdout,"%s -i, --input\t\t\tLocation of the input file.\n",pgname);
	fprintf(stdout,"%s -o, --output\t\t\tLocation of the output file.\n",pgname);
	fprintf(stdout,"%s -a, --action\t\t\tProgram action: encode(default) or decode\n",pgname);

	fprintf(stdout,"Examples:\n");
 
	fprintf(stdout,"%s -a encode -i ~/input -o ~/output\n",pgname);
	fprintf(stdout,"%s -a decode\n",pgname);
	
}

int doDecode(int source[], char decoded[], int amount)
{
	int i,padding,mapped[4];
	padding=0;

	for(i=0;i<4;i++){
		if(source[i]>=65 && source[i]<=90 ){
			mapped[i] = (source[i]-65);
		} else if(source[i] >= 97 && source[i] <= 122 ) {
			mapped[i] = (source[i]-97+26);			
		} else if(source[i]>=48 && source[i]<=57){
			mapped[i] = (source[i]-48+52);	
		} else if(source[i]==43){
			mapped[i] = 62;//'+'
		} else if(source[i]==47){
			mapped[i] = 63;//'/'
		} else if(source[i]==61 && (i==2)){
			if(source[i+1]!=61){
				//error si el tercer byte es padding, el cuarto tiene que serlo
				fprintf(stderr, "Error de padding, si el tercer caracter es '=', el cuerto debe serlo.\n");
				return -1;
			}
			padding++;		
		} else if(source[i]==61 && (i==3)){
			padding++;		
		} else {
			fprintf(stderr, "Caracter invalido (ASCII %d) %c\n", source[i], source[i]);
			return -1;
		}
	}

	// seteo todo temp en 0
	for(i=0; i < amount; i++){ 
		decoded[i]=0;
	}
	
	decoded[0] = mapped[0] << 2;
	decoded[0] |= mapped[1] >> 4;

	decoded[1] = mapped[1] << 4;
	decoded[1] |= mapped[2] >> 2;

	//posiciono los ultimos 4 bits de source[1] en las posiciones 2, 3, 4 y 5 de encoded[2]
	decoded[2] = (mapped[2] << 6); 
	decoded[2] |= mapped[3];
	
	
	return 3-padding;
}


int doEncode(int source[], char encoded[], int amount)
{
	int i, temp[4];

	// seteo todo temp en 0
	for(i=0; i < amount; i++){ 
		temp[i]=0;
	}
	// agrego 0 a la derecha
	for(i=amount; i<3; i++){source[i]=0;}
	
	temp[0] = source[0] >> 2;
	temp[0] &= 63;

	//posiciono los dos ultimos bits de source[0] en bits 2 y 3 de encoded[]
	temp[1] = 63 & (source[0] << 4);
	//copio los primeros 4 bits de source[2] en los ultimos lugares de encoded[1]
	temp[1] |= (63 & (source[1] >> 4));

	//posiciono los ultimos 4 bits de source[1] en las posiciones 2, 3, 4 y 5 de encoded[2]
	temp[2] = 63 & (source[1] << 2); 
	temp[2] |= 63 & (source[2] >> 6);


	temp[3] = 63 & source[2];//elimino los dos bit mas significativos y copio el resto como ultimo encoded[3]

	// mapeo los valores
	for(i=0;i<4;i++){
		if(temp[i]<=25 ){
			encoded[i] = (temp[i]+65);
		} else if(temp[i] <= 51) {
			encoded[i] = (temp[i]+97-26);			
		} else if(temp[i]<=61){
			encoded[i] = (temp[i]+48-52);	
		} else if(temp[i]==62){
			encoded[i] = 43;//'+'
		} else if(temp[i]==63){
			encoded[i] = 47;//'/'
		}
	}
	
	//padding
	if(amount==1){
		encoded[2]='=';
		encoded[3]='=';
	} else if(amount==2){
		encoded[3]='=';	
	}

	return 3;
}


int encode(int fdin,int fdout)
{
	
    FILE *fpout  = {0};

	int count=0;
	
	if(fdout==1){
		fpout = stdout;
	} else {

		fpout = fdopen(fdout,"wb");

		if (fpout == 0) {
		    fprintf(stderr, "Error - Cannot open output stream %d\n", fdout);
		    fprintf(stderr, "Program will close unexpectedly\n");
		    exit(-1);
		}
	}

	
	int buffer[3] = {0};  /* initialized to zeroes */
	char bufferEncoded[3] = {0};
    int i, rc;

    FILE *fp = fdopen(fdin, "rb");
 
    if (fp == NULL) {
        perror("Failed to open input");
        return 1;
    }
	
	 
	while(rc != EOF){
		// leo 3 bytes
    	for (i = 0; i < 3 && (rc = getc(fp)) != EOF ; buffer[i++] = rc) {}
		if(i>0){

			doEncode(buffer, bufferEncoded, i);
			
			if(count >= 76){
				putc('\n', fpout);
				count=0;
			}

			fwrite(bufferEncoded,1,4,fpout);						
			
			count += 4;
		}		

	}
	
	
    // Cerramos los streams
    fclose(fpout);
    close(fdout);
    close(fdin);
	
	return 0;
}

int decode(int fdin,int fdout)
{
	
	int count=0;
    FILE *fpout  = {0};
	
	if(fdout==1){
		fpout = stdout;
	} else {

		fpout = fdopen(fdout,"wb");

		if (fpout == 0) {
		    fprintf(stderr, "Error - Cannot open output stream %d\n", fdout);
		    fprintf(stderr, "Program will close unexpectedly\n");
		    exit(-1);
		}
	}
	
	int buffer[4] = {0};  /* initialized to zeroes */
	char bufferDecoded[4] = {0};
    int i, rc;

    FILE *fp = fdopen(fdin, "rb");
 
    if (fp == NULL) {
        perror("Failed to open input");
        return 1;
    }
 
	while(rc != EOF){
		// leo 4 bytes
		i=0;
	 	
		if(count==76){ //remuevo el salto de linea
			getc(fp);
			count=0;
		}

    	for (i = 0; i < 4 && (rc = getc(fp)) != EOF; buffer[i++] = rc) {}

		if(i>0){
			int a = doDecode(buffer, bufferDecoded, i);

			if(a==-1){
			    fclose(fpout);
			    close(fdout);
				close(fdin);
				exit(1);
			}


			count += 4;

			if(a==-1){
				// caracter invalido, poner stderr y terminar ejecucion	
				return -1;
			}

			for(i=0;i<a;i++){
				putc(bufferDecoded[i], fpout);
			}
		}	
	}
	
	
    // Cerramos los streams
    fclose(fpout);
    close(fdout);
    close(fdin);
	
	return 0;
}

int main(int argc, char *argv[])
{
    int fdin=0;
    int fdout=1;
    int ch;

	static struct option longopts[] = {
             { "version", no_argument, NULL, 'V' },
             { "help", no_argument, NULL, 'h' },
             { "action", required_argument, NULL, 'a' },
             { "input", required_argument, NULL, 'i' },
             { "output", required_argument, NULL, 'o' },
	         { NULL, 0, NULL, 0 }
     };
	
    int action = DEFAULT_ACTION;

    while ((ch = getopt_long(argc, argv, "o:i:a:Vh", longopts, NULL)) != -1)
	{

         switch (ch) {
			case 'o': 
				if (*optarg!='-' && ((fdout = open(optarg, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)) {
					fprintf(stderr, "unable to open %s\n", optarg);
		            usage(argv[0]);
					exit(1);
				}
				break;
			case 'i': 
				if (*optarg!='-' && ((fdin = open(optarg, O_RDONLY, 0)) == -1)) {
					fprintf(stderr, "unable to open %s\n", optarg);
		            usage(argv[0]);
					exit(1);
                }
                break;
			case 'a':
				if(strcmp(optarg, "encode")==0){
					action = ENCODE_ACTION;
				} else if(strcmp(optarg, "decode")==0){
					action = DECODE_ACTION;
				} else {
					fprintf(stderr, "Accion desconocida: %s\n", optarg);
					exit(1);
				}
				break;
            	
            case 'h': 
				usage(argv[0]);
				printf("cantidad de argumentos: %d\n", argc);
            	exit(0);	
      	    	break;
			case 'V':
            	printf("%s, version 1.1\n",argv[0]);	
            	exit(0);
      	    	break;	     
            default:
				fprintf(stderr, "DEFAULT %d\n", ch);
            	usage(argv[0]);
				exit(0);
				break;
         }

	}

	if(action ==  ENCODE_ACTION){
		return encode(fdin, fdout);
	}
	
	return decode(fdin, fdout);
}



